﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;

namespace GBricks.Collections
{
  public static class DictionaryExtensions
  {
    [DebuggerHidden]
    [Pure]
    public static TValue GetValue<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key) {
      Argument.NotNull(source, "source");

      TValue value;
      if(!source.TryGetValue(key, out value)) {
        Throw.KeyNotFound(key);
      }//if

      return value;
    }

    [DebuggerHidden]
    [Pure]
    public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key, TValue @default = default(TValue)) {
      Argument.NotNull(source, "source");

      TValue value;
      if(source.TryGetValue(key, out value)) {
        return value;
      }//if

      return @default;
    }

    [DebuggerHidden]
    [Pure]
    public static TValue? GetValueOrNull<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key, TValue? @default = default(TValue?)) where TValue : struct {
      Argument.NotNull(source, "source");

      TValue value;
      if(source.TryGetValue(key, out value)) {
        return value;
      }//if

      return @default;
    }

    [DebuggerHidden]
    public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key, Func<TKey, TValue> factory) {
      Argument.NotNull(source, "source");
      Argument.NotNull(factory, "factory");

      TValue value;
      if(!source.TryGetValue(key, out value)) {
        value = factory(key);
        source.Add(key, value);
      }//if

      return value;
    }

    [DebuggerHidden]
    public static TValue GetOrAdd<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key, Func<TValue> factory) {
      Argument.NotNull(source, "source");
      Argument.NotNull(factory, "factory");

      TValue value;
      if(!source.TryGetValue(key, out value)) {
        value = factory();
        source.Add(key, value);
      }//if

      return value;
    }

    [DebuggerHidden]
    public static bool TryAdd<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key, TValue value) {
      Argument.NotNull(source, "source");

      if(!source.ContainsKey(key)) {
        source.Add(key, value);
        return true;
      }//if

      return false;
    }

    [DebuggerHidden]
    public static bool TryAdd<TKey, TValue>(this IDictionary<TKey, TValue> source, TKey key, TValue value, out TValue existing) {
      Argument.NotNull(source, "source");

      if(!source.TryGetValue(key, out existing)) {
        source.Add(key, value);
        return true;
      }//if

      return false;
    }

    [DebuggerHidden]
    [Pure]
    public static Dictionary<TKey, TValue> ToDictionary<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> source, IEqualityComparer<TKey> comparer = null) {
      Argument.NotNull(source, "source");
      Contract.Ensures(Contract.Result<Dictionary<TKey, TValue>>() != null);

      return Enumerable.ToDictionary(source, item => item.Key, item => item.Value, comparer);
    }

    [DebuggerHidden]
    [Pure]
    public static IEnumerable<KeyValuePair<TKey, TValue>> Union<TKey, TValue>(this IDictionary<TKey, TValue> first, IEnumerable<KeyValuePair<TKey, TValue>> second) {
      Argument.NotNull(first, "first");
      Contract.Ensures(Contract.Result<IEnumerable<KeyValuePair<TKey, TValue>>>() != null);

      var other =
          from item in second
          where !first.ContainsKey(item.Key)
          select item;
      return first.Concat(other);
    }

    [DebuggerHidden]
    [Pure]
    public static ReadOnlyDictionary<TKey, TValue> AsReadOnly<TKey, TValue>(this IDictionary<TKey, TValue> source) {
      Contract.Ensures(Contract.Result<ReadOnlyDictionary<TKey, TValue>>() != null);

      if(source == null) {
        return EmptyReadOnlyDictionary<TKey, TValue>.Value;
      }//if

      return source as ReadOnlyDictionary<TKey, TValue> ?? new ReadOnlyDictionary<TKey, TValue>(source);
    }

    [Pure]
    public static IDictionary<TKey, TValue> Merge<TKey, TValue>(this IDictionary<TKey, TValue> source, IEnumerable<KeyValuePair<TKey, TValue>> items) {
      Argument.NotNull(source, "source");
      Contract.Ensures(Contract.Result<IDictionary<TKey, TValue>>() != null);

      if(items == null) {
        return source.ToDictionary();
      }//if

      var result = default(IDictionary<TKey, TValue>);

      foreach(var item in items) {
        if(result == null) {
          result = source.ToDictionary();
        }//if

        result[item.Key] = item.Value;
      }//for

      return result ?? new Dictionary<TKey, TValue>();
    }

    #region Ordered Dictionary

    [DebuggerStepThrough]
    public static OrderedDictionary<TKey, TValue> ToOrderedDictionary<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> source, IEqualityComparer<TKey> comparer = null) {
      Argument.NotNull(source, "source");
      Contract.Ensures(Contract.Result<OrderedDictionary<TKey, TValue>>() != null);
      return new OrderedDictionary<TKey, TValue>(comparer) { source, };
    }

    [DebuggerStepThrough]
    public static OrderedDictionary<TKey, TItem> ToOrderedDictionary<TItem, TKey>(this IEnumerable<TItem> source, Func<TItem, TKey> keySelector, IEqualityComparer<TKey> comparer = null) {
      Argument.NotNull(source, "source");
      Argument.NotNull(keySelector, "keySelector");
      Contract.Ensures(Contract.Result<OrderedDictionary<TKey, TItem>>() != null);
      return source.Select(item => Pair.Create(keySelector(item), item)).ToOrderedDictionary(comparer);
    }

    [DebuggerStepThrough]
    public static OrderedDictionary<TKey, TValue> ToOrderedDictionary<TItem, TKey, TValue>(this IEnumerable<TItem> source, Func<TItem, TKey> keySelector, Func<TItem, TValue> valueSelector, IEqualityComparer<TKey> comparer = null) {
      Argument.NotNull(source, "source");
      Argument.NotNull(keySelector, "keySelector");
      Argument.NotNull(valueSelector, "valueSelector");
      Contract.Ensures(Contract.Result<OrderedDictionary<TKey, TValue>>() != null);
      return source.Select(item => Pair.Create(keySelector(item), valueSelector(item))).ToOrderedDictionary(comparer);
    }

    [DebuggerStepThrough]
    public static ReadOnlyOrderedDictionary<TKey, TValue> AsReadOnly<TKey, TValue>(this IOrderedDictionary<TKey, TValue> source) {
      Contract.Ensures(Contract.Result<ReadOnlyOrderedDictionary<TKey, TValue>>() != null);
      return new ReadOnlyOrderedDictionary<TKey, TValue>(source);
    }

    #endregion Ordered Dictionary

    private static class EmptyReadOnlyDictionary<TKey, TValue>
    {
      private static readonly ReadOnlyDictionary<TKey, TValue> Empty = new ReadOnlyDictionary<TKey, TValue>(new Dictionary<TKey, TValue>());

      public static ReadOnlyDictionary<TKey, TValue> Value {
        [DebuggerStepThrough]
        get {
          Contract.Ensures(Contract.Result<ReadOnlyDictionary<TKey, TValue>>() != null);
          return Empty;
        }
      }
    }
  }
}
